#!/usr/bin/env python2.6
"""XXX

#. for language in languages:
        for example in examples:
            if example in language:
                example.test(language)
            else:
                XXX example not implemented for the language

"""
from __future__ import with_statement

from optparse import OptionParser
import os
import os.path
from string import Template
import subprocess
import sys

import console
from docutils.core import publish_doctree
import yaml


class Language(object):

    def __init__(self, name, cwd=os.getcwd()):
        """Initialize the object, raising ValueError if the language does not
        exist."""
        self.directory = os.path.join(cwd, name)
        info_path = os.path.join(self.directory, 'INFO.yml')
        if not os.path.exists(info_path):
            raise ValueError('configuration data for %s does not exist' % name)
        with open(info_path) as file_:
            info = yaml.load(file_)
        self.name = name
        self.extension = info['Extension']
        if 'run' in info:
            self.run = Template(info['run'])
        elif 'interpreter' in info:
            self.run = Template("%s $SOURCE $ARGUMENTS" % info['interpreter'])
        else:
            self.run = Template("./%s/${EXAMPLE} $ARGUMENTS" % self.name)

    def __contains__(self, example):
        """See if the example is implemented in the language."""
        source_file = os.path.join(self.directory,
                                    '.'.join([example.name, self.extension]))
        return os.path.exists(source_file)

    def command(self, example):
        """Return the string to use to execute the example."""
        kwargs = {'EXAMPLE': example.name,
                'SOURCE': os.path.join(self.directory,
                                    '.'.join([example.name, self.extension]))}
        return Template(self.run.safe_substitute(**kwargs))

    @classmethod
    def known_languages(cls, cwd=os.getcwd()):
        """Return the set of known languages."""
        found = set()
        for entry in os.listdir(cwd):
            path = os.path.join(cwd, entry)
            if os.path.isdir(path):
                if not entry.startswith('.') and not entry.startswith('_'):
                    found.add(cls(entry, cwd))
        return found


class Example(object):

    def __init__(self, name, cwd=os.getcwd()):
        """Initialize the object, raising ValueError if the language does not
        exist."""
        rst_path = os.path.join(cwd, name+'.rst')
        if not os.path.exists(rst_path):
            raise ValueError('%s does not exist' % rst_path)
        self.name = name
        with open(rst_path) as file_:
            doctree = publish_doctree(file_.read())
        for child in doctree.children:
            if child.tagname == u'admonition':
                if child.children[0].astext() == u'Deprecated':
                    self.deprecated = True
                    break
        else:
            self.deprecated = False
        examples_section = doctree.children[-1].children
        assert examples_section[0].tagname == u'title'
        assert examples_section[0].astext() == u'Examples'
        hygenic_data = (x.astext() for x in examples_section
                        if x.tagname in ('paragraph', 'literal_block'))
        ex_iter = iter(hygenic_data)
        self.tests = []
        try:
            while True:
                input_ = ex_iter.next()
                assert input_.endswith(':')
                input_ = input_[:-1]
                if input_.startswith('?'):
                    input_ = input_[1:]
                    optional = True
                else:
                    optional = False
                if input_ == '-':
                    input_ = ''
                output = ex_iter.next()
                self.tests.append((input_, output, optional))
        except StopIteration:
            pass

    def test(self, language):
        """Run the language's implementation of the example."""
        run_template = language.command(self)
        for input_, output, optional in self.tests:
            command = run_template.substitute(ARGUMENTS=input_)
            proc = subprocess.Popen(command, shell=True,
                    #cwd=language.directory,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
            proc.wait()
            stderr = proc.stderr.read()
            if proc.returncode or stderr:
                raise RuntimeError(stderr or proc.returncode)
            result = proc.stdout.read().strip()
            if result == output:
                yield 'passed', None, None, None
            elif optional:
                yield 'optional', input_, output, result
            else:
                yield 'failed', input_, output, result

    @classmethod
    def known_examples(cls, cwd=os.getcwd()):
        """Return the set of known examples."""
        found = set()
        for entry in os.listdir(cwd):
            path = os.path.join(cwd, entry)
            if os.path.isfile(path) and entry.endswith('.rst'):
                found.add(cls(os.path.splitext(entry)[0], cwd))
        return found


def main(args):
    """Run the specified examples against the specified tests based on the
    command-line arguments."""
    arg_parser = OptionParser()
    arg_parser.add_option('-e', '--example', help="specify an example to test",
                            action='store', type='string')
    arg_parser.add_option('-v', '--verbose', help="display error output",
                            action='store_true', default=False)
    arg_parser.add_option('-c', '--check',
                          help="check what examples have not been implemented",
                          action='store_true', default=False)
    options, args = arg_parser.parse_args(args)
    if not options.example:
        examples = Example.known_examples()
    else:
        examples = set([Example(options.example)])
    if not args:
        languages = Language.known_languages()
    else:
        languages = set(map(Language, args))
    for language in sorted(languages, key=lambda x: x.name):
        print language.name
        complete = True
        for example in examples:
            if not example in language:
                if not example.deprecated:
                    print '    %s %s' %(example.name,
                                         console.red('is not implemented.'))
                    complete = False
                continue
            elif options.check:
                continue
            print '   ',
            if example.deprecated:
                print console.brown(example.name),
            else:
                print example.name,
            for result, input_, output, got in example.test(language):
                sys.stdout.flush()
                if result == 'passed':
                    print '.',
                elif result == 'optional':
                    print console.teal('_'),
                else:
                    print console.red('x'),
                    if options.verbose:
                        print 'input:', repr(input_)
                        print 'expected:', repr(output)
                        print 'got:', repr(got)
                        break
            else:
                print
        else:
            if options.check and complete:
                print '    ', console.green("Completed.")


if __name__ == '__main__':
    main(sys.argv[1:])
